package com.zcxy.mybatis.util.mapper;

import com.baomidou.mybatisplus.annotation.TableId;
import com.zcxy.exception.BusinessException;
import com.zcxy.mybatis.util.anotation.*;
import com.zcxy.mybatis.util.bean.InverseJoinColumnen;
import com.zcxy.mybatis.util.bean.JoinColumnen;
import com.zcxy.mybatis.util.bean.JoinTableEn;
import com.zcxy.mybatis.util.bean.Relation;
import com.zcxy.mybatis.util.enums.FieldCollectionType;
import com.zcxy.mybatis.util.enums.RelationType;
import lombok.Data;
import org.springframework.util.StringUtils;

import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.net.JarURLConnection;
import java.net.URL;
import java.util.*;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
 * AutoMapper for one2one/one2many/many2many
 *
 * @author zcc
 */
@Data
public class AutoMapper extends AbstractAutoMapper {
    private String[] i1;

    public AutoMapper() {
        this.i1 = new String[]{};
    }

    public AutoMapper(String[] i1) {
        this.i1 = i1;
    }

    public void initData(String[] i1) {
        if (i1 != null && i1.length > 0) {
            for (int i = 0; i < i1.length; i++) {
                String $_2 = i1[i];
                try {
                    Set<Class<?>> classes = getClasses($_2);
                    classes.stream().forEach(
                            clazz -> {
                                try {
                                    List<Relation> k3s = new ArrayList<>();
                                    Field[] k2 = clazz.getDeclaredFields();
                                    if (k2 != null && k2.length > 0) {
                                        for (int j = 0; j < k2.length; j++) {
                                            Field k10 = k2[j];
                                            OneToOne O1 = k10.getAnnotation(OneToOne.class);

                                            OneToMany O2 = k10.getAnnotation(OneToMany.class);

                                            ManyToOne O3 = k10.getAnnotation(ManyToOne.class);

                                            ManyToMany O4 = k10.getAnnotation(ManyToMany.class);

                                            Relation k3 = new Relation();
                                            TableId tableId = k10.getAnnotation(TableId.class);
                                            if (tableId != null) {
                                                k3.setTableId(k10.getName());
                                            }
                                            if (O1 != null) {
                                                k3.setRelationType(RelationType.ONETOONE);
                                                if (O1.targetEntity() != void.class) {
                                                    k3.setMapperClazz(O1.targetEntity());
                                                }
                                            } else if (O2 != null) {
                                                k3.setRelationType(RelationType.ONETOMANY);
                                                if (O2.targetEntity() != void.class) {
                                                    k3.setMapperClazz(O2.targetEntity());
                                                }
                                            } else if (O3 != null) {
                                                k3.setRelationType(RelationType.MANYTOONE);
                                                if (O3.targetEntity() != void.class) {
                                                    k3.setMapperClazz(O3.targetEntity());
                                                }
                                            } else if (O4 != null) {
                                                k3.setRelationType(RelationType.MANYTOMANY);
                                                if (O4.targetEntity() != void.class) {
                                                    k3.setMapperClazz(O4.targetEntity());
                                                }
                                            } else {
                                                continue;
                                            }
                                            if (k3.getMapperClazz() == null) {
                                                k3.setMapperClazz(getFiledClass(k10));
                                            }
                                            JoinTable k12 = k10.getAnnotation(JoinTable.class);

                                            if (O4 != null) {
                                                $f_1(k12, k3, $_2 + clazz.getName());
                                            }
                                            //初始化其他数据
                                            initK1m5(k10, k3, $_2 + clazz.getName());
                                            initK1m6(k10, k3);
                                            initK1m7(k10, k3);
                                            //初始化关联关系
                                            k3s.add(k3);
                                        }
                                    }
                                    this.getR1().put(clazz.getSimpleName(), k3s);
                                } catch (Exception e) {
                                    e.printStackTrace();
                                    throw new BusinessException("Error in scan entity bean");
                                }

                            }
                    );
                } catch (IOException e) {
                    e.printStackTrace();
                }

            }

        }
    }

    private void $f_1(JoinTable k12, Relation k3, String voname) {
        JoinTableEn k11 = new JoinTableEn();

        k11.setRelationClazz(k12.entityClass());

        Class k1m = null;
        String k1m2 = k12.entityClass().getSimpleName();
        Collection<Class<?>> k1m3 = this.factory.getObject().getConfiguration().getMapperRegistry().getMappers();
        for (Class<?> mapperClz : k1m3) {
            String k1mName = mapperClz.getSimpleName();
            if (k1mName.equalsIgnoreCase(k1m2 + "mapper")) {
                k1m = mapperClz;
                break;
            }
        }
        if (k1m == null) {
            throw new BusinessException(voname + "中@JoinTable指定的entityClass非数据库映射字段");
        }
        k11.setRelationMapper(k1m);
        k3.setJoinTableEn(k11);

    }

    public Class getFiledClass(Field k10) {
        boolean isCollection = k10.getType() == List.class || k10.getType() == ArrayList.class
                || k10.getType() == Set.class || k10.getType() == HashSet.class;
        Class k10Class = k10.getType();
        if (isCollection) {
            Type k1m1 = k10.getGenericType();
            if (k1m1 != null && k1m1 instanceof ParameterizedType) {
                ParameterizedType pt = (ParameterizedType) k1m1;
                k10Class = (Class<?>) pt.getActualTypeArguments()[0];
            }
        }
        return k10Class;
    }

    public void initK1m5(Field k10, Relation k3, String voName) {
        String name = k10.getName();
        boolean isCollection = k10.getType() == List.class || k10.getType() == ArrayList.class
                || k10.getType() == Set.class || k10.getType() == HashSet.class;
        Class k10Class = k10.getType();
        if (isCollection) {
            Type k1m1 = k10.getGenericType();
            if (k1m1 != null && k1m1 instanceof ParameterizedType) {
                ParameterizedType pt = (ParameterizedType) k1m1;
                k10Class = (Class<?>) pt.getActualTypeArguments()[0];
            }
        }
        k3.setFiledName(name);
        if (k10.getType() == List.class || k10.getType() == ArrayList.class) {
            k3.setFieldCollectionType(FieldCollectionType.LIST);
        } else if (k10.getType() == Set.class || k10.getType() == HashSet.class) {
            k3.setFieldCollectionType(FieldCollectionType.SET);
        } else {
            k3.setFieldCollectionType(FieldCollectionType.NONE);
        }
        k3.setEntityClazz(k10Class);
        Class k1m = null;
        String k1m2 = k3.getMapperClazz() != null ? k3.getMapperClazz().getSimpleName() : k10Class.getSimpleName();
        Collection<Class<?>> k1m3 = this.factory.getObject().getConfiguration().getMapperRegistry().getMappers();
        for (Class<?> mapperClz : k1m3) {
            String k1mName = mapperClz.getSimpleName();
            if (k1mName.equalsIgnoreCase(k1m2 + "mapper")) {
                k1m = mapperClz;
                break;
            }
        }
        if (k1m == null) {
            throw new BusinessException(
                    "[Class:初始化" + voName + "对象出错,请指定" + voName + "中" + "注解" + k3.getRelationType().name() + "的targetEntity值为数据库对象");
        }
        k3.setMapper(k1m);
    }


    public void initK1m6(Field k10, Relation k3) {
        JoinColumn k13 = k10.getAnnotation(JoinColumn.class);
        JoinColumns k13s = k10.getAnnotation(JoinColumns.class);

        List<JoinColumnen> k13ens = new ArrayList<>();
        if (k13 != null) {
            JoinColumnen k13en = new JoinColumnen();
            k13en.setName(k13.name());
            if (StringUtils.isEmpty(k13.referencedColumnName())) {
                k13en.setReferencedColumnName(HumpToUnderline(k3.getTableId()));

            } else {
                k13en.setReferencedColumnName(k13.referencedColumnName());
            }
            k13ens.add(k13en);
        }
        if (k13s != null) {
            for (int i = 0; i < k13s.value().length; i++) {
                JoinColumnen k13en = new JoinColumnen();
                k13en.setName(k13s.value()[i].name());
                k13en.setReferencedColumnName(k13s.value()[i].referencedColumnName());
                k13ens.add(k13en);
            }
        }
        k3.setJoinColumnens(k13ens);
    }


    public void initK1m7(Field k10, Relation k3) {

        InverseJoinColumn k13 = k10.getAnnotation(InverseJoinColumn.class);

        InverseJoinColumns k13s = k10.getAnnotation(InverseJoinColumns.class);

        List<InverseJoinColumnen> k13ens = new ArrayList<>();
        if (k13 != null) {
            InverseJoinColumnen k13en = new InverseJoinColumnen();
            k13en.setName(k13.name());
            k13en.setReferencedColumnName(k13.referencedColumnName());
            k13ens.add(k13en);
        }
        if (k13s != null) {
            for (int i = 0; i < k13s.value().length; i++) {
                InverseJoinColumnen k13en = new InverseJoinColumnen();
                k13en.setName(k13s.value()[i].name());
                k13en.setReferencedColumnName(k13s.value()[i].referencedColumnName());
                k13ens.add(k13en);
            }
        }
        k3.setInverseJoinColumns(k13ens);

    }


    private static void addClass(Set<Class<?>> classSet, String packagePath, String packageName) {
        File[] files = new File(packagePath).listFiles(new FileFilter() {
            @Override
            public boolean accept(File file) {
                return (file.isFile() && file.getName().endsWith(".class")) || file.isDirectory();
            }
        });
        for (File file : files) {
            String fileName = file.getName();
            if (file.isFile()) {
                String className = fileName.substring(0, fileName.lastIndexOf("."));
                if (!StringUtils.isEmpty(packageName)) {
                    className = packageName + "." + className;
                }
                doAddClass(classSet, className);
            } else {
                String subPackagePath = fileName;
                if (!StringUtils.isEmpty(packagePath)) {
                    subPackagePath = packagePath + "/" + subPackagePath;
                }
                String subPackageName = fileName;
                if (!StringUtils.isEmpty(packageName)) {
                    subPackageName = packageName + "." + subPackageName;
                }
                addClass(classSet, subPackagePath, subPackageName);
            }
        }
    }

    /**
     * 加载类
     */
    public static Class<?> loadClass(String className, boolean isInitialized) {
        Class<?> cls;
        try {
            cls = Class.forName(className, isInitialized, Thread.currentThread().getContextClassLoader());
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
        return cls;
    }

    /**
     * 加载类（默认将初始化类）
     */
    public static Class<?> loadClass(String className) {
        return loadClass(className, true);
    }

    private static void doAddClass(Set<Class<?>> classSet, String className) {
        Class<?> cls = loadClass(className, false);
        classSet.add(cls);
    }


    public static Set<Class<?>> getClasses(String packageName) throws IOException {
        Set<Class<?>> classSet = new HashSet<>();
        Enumeration<URL> urls = Thread.currentThread().getContextClassLoader().getResources(packageName.replace(".", "/"));
        while (urls.hasMoreElements()) {
            URL url = urls.nextElement();
            if (url != null) {
                String protocol = url.getProtocol();
                if (protocol.equals("file")) {
                    String packagePath = url.getPath().replaceAll("%20", " ");
                    addClass(classSet, packagePath, packageName);
                } else if (protocol.equals("jar")) {
                    JarURLConnection jarURLConnection = (JarURLConnection) url.openConnection();
                    if (jarURLConnection != null) {
                        JarFile jarFile = jarURLConnection.getJarFile();
                        if (jarFile != null) {
                            Enumeration<JarEntry> jarEntries = jarFile.entries();
                            while (jarEntries.hasMoreElements()) {
                                JarEntry jarEntry = jarEntries.nextElement();
                                String jarEntryName = jarEntry.getName();
                                if (jarEntryName.endsWith(".class")) {
                                    String className = jarEntryName.substring(0, jarEntryName.lastIndexOf(".")).replaceAll("/", ".");
                                    doAddClass(classSet, className);
                                }
                            }
                        }
                    }
                }
            }
        }

        return classSet;
    }


}
